use std::{ptr, slice};

use super::{super::serial_port::RecvFrame, global::get_rt};
use crate::{
    error::{new_err, AppErr},
    serial_port::{new_serial_port, SharedConn}, utils::path::IO_EXCEPTION,
};
use jni::{
    objects::{JByteArray, JObject, JString, ReleaseMode},
    sys::{jbyteArray, jint},
    JNIEnv,
};
use tokio::sync::Mutex;


static CONN: Mutex<Option<SharedConn>> = Mutex::const_new(None);

async fn get_conn() -> Result<SharedConn, AppErr> {
    let conn = CONN.lock().await;
    if let Some(conn) = conn.as_ref() {
        return Ok(conn.clone());
    }
    Err(new_err("串口没有打开"))
}

fn open_impl(env: &mut JNIEnv, name: JString, baud_rate: jint, cb: JObject) -> Result<(), AppErr> {
    let name = unsafe { env.get_string_unchecked(&name)? };
    let name = name.to_str().map_err(|_| new_err("utf8 err"))?;
    let cb = env.new_global_ref(cb)?;

    get_rt().block_on(async {
        let mut p = CONN.lock().await;
        if p.is_some() {
            return Err(new_err("串口已经打开"));
        }
        match new_serial_port(name, baud_rate as u32, cb) {
            Ok(conn) => {
                *p = Some(conn);
                Ok(())
            }
            Err(e) => Err(e),
        }
    })?;

    Ok(())
}

#[no_mangle]
pub extern "system" fn Java_rustlib_SerialPort2_00024Companion_open(
    mut env: JNIEnv,
    _obj: JObject,
    name: JString,
    baud_rate: jint,
    cb: JObject,
) {
    if let Err(e) = open_impl(&mut env, name, baud_rate, cb) {
        _ = env.throw_new(IO_EXCEPTION, e.to_string());
    }
}

#[no_mangle]
pub extern "system" fn Java_rustlib_SerialPort2_ping(mut env: JNIEnv, _obj: JObject) {
    if let Err(e) = get_rt().block_on(async {
        let conn = get_conn().await?;
        conn.ping().await?;
        Result::<(), AppErr>::Ok(())
    }) {
        _ = env.throw_new(IO_EXCEPTION, e.to_string());
    }
}

fn simple_req_impl(env: &mut JNIEnv, cmd: jint, args: JByteArray) -> Result<jbyteArray, AppErr> {
    let args = unsafe { env.get_array_elements(&args, ReleaseMode::NoCopyBack)? };
    let args = unsafe { slice::from_raw_parts(args.as_ptr() as *const u8, args.len()) };
    let frame = get_rt().block_on(async {
        let conn = get_conn().await?;
        let frame = conn.simple_req(cmd as u8, args).await?;
        Result::<RecvFrame, AppErr>::Ok(frame)
    })?;

    Ok(frame.new_jbyte_array2(env)?.as_raw())
}

#[no_mangle]
pub extern "system" fn Java_rustlib_SerialPort2_simpleReq(
    mut env: JNIEnv,
    _obj: JObject,
    cmd: jint,
    args: JByteArray,
) -> jbyteArray {
    match simple_req_impl(&mut env, cmd, args) {
        Ok(v) => v,
        Err(e) => {
            _ = env.throw_new(IO_EXCEPTION, e.to_string());
            ptr::null_mut()
        }
    }
}

fn req_impl(env: &mut JNIEnv, cmd: jint, args: JByteArray) -> Result<jbyteArray, AppErr> {
    let args = unsafe { env.get_array_elements(&args, ReleaseMode::NoCopyBack)? };
    let args = unsafe { slice::from_raw_parts(args.as_ptr() as *const u8, args.len()) };
    let frame = get_rt().block_on(async {
        let conn = get_conn().await?;
        let frame = conn.request(cmd as u8, args).await?;
        Result::<RecvFrame, AppErr>::Ok(frame)
    })?;

    Ok(frame.new_jbyte_array2(env)?.as_raw())
}

#[no_mangle]
pub extern "system" fn Java_rustlib_SerialPort2_req(
    mut env: JNIEnv,
    _obj: JObject,
    cmd: jint,
    args: JByteArray,
) -> jbyteArray {
    match req_impl(&mut env, cmd, args) {
        Ok(v) => v,
        Err(e) => {
            _ = env.throw_new(IO_EXCEPTION, e.to_string());
            ptr::null_mut()
        }
    }
}

pub extern "system" fn Java_rustlib_SerialPort2_close(_env: JNIEnv, _obj: JObject) {
    get_rt().block_on(async {
        let mut p = CONN.lock().await;
        if let Some(p) = p.as_ref() {
            p.close()
        }
        *p = None;
    });
}
